package com.dmj.sqldsl.executor.visitor;

import static java.util.stream.Collectors.joining;

import com.dmj.sqldsl.executor.exception.UnsupportedColumnException;
import com.dmj.sqldsl.executor.exception.UnsupportedConditionException;
import com.dmj.sqldsl.executor.exception.UnsupportedConditionMethodException;
import com.dmj.sqldsl.executor.exception.UnsupportedFunctionException;
import com.dmj.sqldsl.executor.exception.UnsupportedTableException;
import com.dmj.sqldsl.model.Where;
import com.dmj.sqldsl.model.column.AliasColumn;
import com.dmj.sqldsl.model.column.Column;
import com.dmj.sqldsl.model.column.Function;
import com.dmj.sqldsl.model.column.FunctionColumn;
import com.dmj.sqldsl.model.column.ListValueColumn;
import com.dmj.sqldsl.model.column.SimpleColumn;
import com.dmj.sqldsl.model.column.SubQueryValueColumn;
import com.dmj.sqldsl.model.column.ValueColumn;
import com.dmj.sqldsl.model.condition.And;
import com.dmj.sqldsl.model.condition.Condition;
import com.dmj.sqldsl.model.condition.ConditionElement;
import com.dmj.sqldsl.model.condition.ConditionMethod;
import com.dmj.sqldsl.model.condition.Conditions;
import com.dmj.sqldsl.model.condition.Or;
import com.dmj.sqldsl.model.table.SimpleTable;
import com.dmj.sqldsl.model.table.SubQueryTable;
import com.dmj.sqldsl.model.table.Table;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Getter;

@Getter
public abstract class ModelVisitor {

  protected final List<Parameter> params;

  public ModelVisitor() {
    this.params = new ArrayList<>();
  }

  protected String visit(Column column) {
    if (column instanceof SimpleColumn) {
      return visit((SimpleColumn) column);
    } else if (column instanceof ListValueColumn) {
      return visit((ListValueColumn) column);
    } else if (column instanceof ValueColumn) {
      return visit((ValueColumn) column);
    } else if (column instanceof FunctionColumn) {
      return visit((FunctionColumn) column);
    } else if (column instanceof SubQueryValueColumn) {
      return visit((SubQueryValueColumn) column);
    } else if (column instanceof AliasColumn) {
      return visit((AliasColumn) column);
    }
    throw new UnsupportedColumnException(column);
  }

  protected String visit(Table table) {
    if (table instanceof SimpleTable) {
      return visit((SimpleTable) table);
    } else if (table instanceof SubQueryTable) {
      return visit((SubQueryTable) table);
    }
    throw new UnsupportedTableException(table);
  }

  protected String visit(ConditionElement element) {
    if (element instanceof Conditions) {
      return String.format("(%s)", visit((Conditions) element));
    } else if (element instanceof Condition) {
      return visit((Condition) element);
    } else if (element instanceof Or) {
      return visit((Or) element);
    } else if (element instanceof And) {
      return visit((And) element);
    }
    throw new UnsupportedConditionException(element);
  }

  protected String visit(ConditionMethod method) {
    switch (method) {
      case eq:
        return "=";
      case ge:
        return ">=";
      case gt:
        return ">";
      case in:
        return "in";
      case le:
        return "<=";
      case lt:
        return "<";
      case ne:
        return "!=";
      case like:
        return "like";
      case notLike:
        return "not like";
      case notIn:
        return "not in";
      default:
        throw new UnsupportedConditionMethodException(method);
    }
  }

  protected String visit(Where where) {
    return String.format(" where %s", visit(where.getConditions()));
  }

  protected abstract String visit(SubQueryValueColumn column);

  protected String visit(FunctionColumn column) {
    String paramsSql = column.getParams().stream().map(this::visit).collect(joining(","));
    return String.format("%s(%s)", visit(column.getFunction()), paramsSql);
  }

  protected String visit(Function function) {
    switch (function) {
      case count:
        return "count";
      case sum:
        return "sum";
      case avg:
        return "avg";
      case max:
        return "max";
      case min:
        return "min";
      case distinct:
        return "distinct";
      default:
        throw new UnsupportedFunctionException(function);
    }
  }

  protected String visit(Conditions conditions) {
    List<ConditionElement> conditionElements = conditions.getConditionElements();
    return conditionElements.stream()
        .map(this::visit)
        .collect(joining(" "));
  }

  protected String visit(Condition condition) {
    return String.format("%s %s %s",
        visit(condition.getLeft()),
        visit(condition.getMethod()),
        visit(condition.getRight()));
  }

  protected String visit(And and) {
    return "and";
  }

  protected String visit(Or or) {
    return "or";
  }

  protected String visit(SimpleColumn column) {
    return column.getColumnName();
  }

  protected String visit(ValueColumn column) {
    params.add(new Parameter(column.getValue()));
    return "?";
  }

  protected String visit(ListValueColumn column) {
    return column.getList().stream().map(x -> {
      params.add(new Parameter(x));
      return "?";
    }).collect(Collectors.joining(",", "(", ")"));
  }

  protected String visit(AliasColumn column) {
    return String.format("%s as %s", visit(column.getColumn()), column.getAlias());
  }

  protected String visit(SimpleTable table) {
    return table.getTableName();
  }

  protected abstract String visit(SubQueryTable table);

}
